package com.yuanxiao.common.redis.utils;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class ResourceLock implements AutoCloseable {

    protected static Logger logger = LoggerFactory.getLogger(ResourceLock.class);

    private final RLock multiLock;

    /**
     * Redisson 的 RLock multiLock = getMultiLock(...) 是“多锁全加”机制，即：
     *  只要其中一个锁被其他线程持有，整个 multiLock.lock() 操作就会阻塞，直到所有锁都可用。
     * 所以：
     *  - 线程 A 持有 [ck1001, sp001]
     *  - 线程 B 尝试获取 [ck1001, sp001] + [ck1001, sp003]
     *  - 因为 [ck1001, sp001] 被占用 → 整个 multiLock.lock() 会阻塞
     *  - 线程 B 必须等线程 A 释放 [ck1001, sp001] 后才能继续执行
     */
    public ResourceLock(RedissonClient redissonClient, List<LockResource> resources) {
        /**
         * 一个单据中会有重复的 数据ID，有可能出现报错情况。
         * 在使用 Redisson 的 getMultiLock() 时，如果传入多个相同的锁（即相同 key）
         */
        List<String> sortedKeys = resources.stream()
                .map(r -> buildLockKey(r.lockKey))
                .distinct()
                .sorted() // 统一加锁顺序，避免死锁
                .collect(Collectors.toList());

        List<RLock> locks = sortedKeys.stream()
                .map(redissonClient::getLock)
                .collect(Collectors.toList());

        if (sortedKeys.size() < resources.size()) {
            logger.warn("发现 {} 个重复的 LockResource 锁 key", resources.size() - sortedKeys.size());
        }

        this.multiLock = redissonClient.getMultiLock(locks.toArray(new RLock[0]));
    }

    public void lock() {
        multiLock.lock(); // 原子性加锁，全部成功才继续
    }

    /**
     * 尝试加锁，如果加锁失败，则抛出异常
     * @param waitTime
     * @param leaseTime
     * @param unit
     * @return
     * @throws InterruptedException
     */
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        return multiLock.tryLock(waitTime, leaseTime, unit);
    }

    /*@Override
    public void close() {
        if (multiLock.isHeldByCurrentThread()) {
            multiLock.unlock();
            logger.debug("锁已释放");
        } else {
            logger.warn("尝试释放未持有的锁");
        }
    }*/

    @Override
    public void close() {
        try {
            multiLock.unlock();
            logger.debug("锁已释放");
        } catch (IllegalMonitorStateException e) {
            // 捕获释放未持有的锁时抛出的异常
            logger.warn("尝试释放未持有的锁", e);
        }
    }

    private String buildLockKey(String lockKey) {
        return String.format("lock:key:%s", lockKey);
    }

    public static class LockResource {
        private final String lockKey;

        public LockResource(String lockKey) {
            this.lockKey = lockKey;
        }

        public static LockResource of(String lockKey) {
            return new LockResource(lockKey);
        }
    }
}

